﻿using BinanceExchange.API.Client;
using BinanceExchange.API.Enums;
using log4net;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Linq;
using System.Threading.Tasks;
using System.Timers;
using BinanceExchange.API.Models.Request;
using System.ComponentModel;
using QntPlatform.Db;

namespace QntPlatform.BinanceApi
{


    // 交易者
    [Description("Binance")]
    public class BinanceExchangeImp : IExchange
    {
        public string Name { get => "Binance"; }
        //交易所配置，密钥，交易对
        //UserPlatform platform;
        string currency;
        BinanceClient client;
        JFApiClient jfclient;
        readonly string asset = "USDT";
        public static bool isTest = true;

        public string Currency { get => currency; private set => currency = value; }
        LogInfo LogType = new LogInfo() { LogType = "Exchange" };

        public BinanceExchangeImp(string apiKey, string secretKey, string currency, ILog _log = null)
        {
            if (_log != null)
            {
                log = _log;
            }
            else
            {
                log = LogManager.GetLogger();
            }
            client = new BinanceClient(new ClientConfiguration()
            {
                ApiKey = apiKey,
                SecretKey = secretKey,
                Logger = log4net.LogManager.GetLogger(typeof(BinanceExchangeImp))
            });
            this.Currency = currency;
            jfclient = new JFApiClient(apiKey, secretKey);
            tickerListener = TickerListener.GetOrCreateListener(this);
            //tickerListener.InitTickerChange();
            //tickerChangeEvent=tickerListener.tickerChangeEvent;
            // tickerListener.TickerChangeEvent+=(p1,p2)=>{
            //    if(tickerChangeEvent!=null) tickerChangeEvent(p1,p2);
            // };
            //InitTickerChange();
            log.Info($"交易平台接口初始化完成：{this.GetHashCode()},apikey:{apiKey},环境：", isTest, msg3: LogType);
        }
        TickerListener tickerListener;
        public async Task<string> ConnTestAsync()
        {

            var re = await GetAccountAsync();
            var info = "连通性检测,余额:" + re.Balance;
            //log.Info(info);
            return info;
        }
        EventHandler<Ticker> tickerChangeEvent;
        public event EventHandler<Ticker> TickerChangeEvent
        {
            add
            {
                // if (!timer.Enabled)
                // {
                //     timer.Start();
                // }
                //tickerChangeEvent += value;
                tickerListener.TickerChangeEvent += value;
            }
            remove
            {
                //tickerChangeEvent -= value;
                // if (tickerChangeEvent == null || tickerChangeEvent.GetInvocationList().Length < 1)
                // {
                //     timer.Stop();
                // }
                tickerListener.TickerChangeEvent -= value;

            }
        }

        Ticker oldTck;
        public void InitTickerChange()
        {
            timer = new Timer(2000);
            timer.Elapsed += (p1, p2) =>
            {
                try
                {
                    var tck = GetTickerAsync().Result;
                    if (oldTck?.Buy == tck.Buy)
                    {
                        return;
                    }
                    if (tickerChangeEvent != null)
                        tickerChangeEvent(this, tck);
                    oldTck = tck;
                }
                catch (Exception ex)
                {
                    log.Error(ex, msg3: LogType);
                    throw;
                };
            };
        }
        Timer timer;
        /// <summary>
        /// 只实现了，可以余额Balance
        /// </summary>
        /// <returns></returns>
        public async Task<Account> GetAccountAsync()
        {
            var re = await client.GetBalance(5000);
            var info = re.First(p => p.asset == asset);//Currency
            var acc = new Account();
            acc.Balance = info.availableBalance;
            acc.Info = info;
            return acc;
        }
        public async Task<Ticker> GetTickerAsync()
        {
            try
            {
                var resp = await client.GetSymbolsPriceTicker(Currency); //client.GetPrice(currency);
                                                                         //var  resp = re.FirstOrDefault();
                var tck = new Ticker();
                tck.Buy = resp.Price;
                tck.Sell = resp.Price;
                tck.Last = resp.Price;
                return tck;
            }
            catch (Exception ex)
            {
                System.Console.WriteLine(ex);
                log.Error(ex, msg3: LogType);
                throw;
            }

        }
        public async Task<Record[]> GetRecordsAsync(int period, int limit = 30)
        {
            var list = await client.GetKlinesCandlesticks(new BinanceExchange.API.Models.Request.GetKlinesCandlesticksRequest() { Symbol = Currency, Interval = periodConvert(period), Limit = limit });
            var re = list.Select(p => new Record() { Close = p.Close, Volume = p.Volume, High = p.High, Low = p.Low, Open = p.Open, Time = p.CloseTime.Ticks }).ToArray();
            return re;
        }

        /*
        60	1m	OneMinute,
3*60	"3m"	ThreeMinutes,
5*60	"5m"	FiveMinutes,
15*60	"15m"	FifteenMinutes,
30*60	"30m"	ThirtyMinutes,
3600	"1h"	OneHour,
2*3600	"2h"	TwoHours,
4*3600	"4h"	FourHours,
6*3600	"6h"	SixHours,
8*3600	"8h"	EightHours,
12*3600	"12h"	TwelveHours,
24*3600	"1d"	OneDay,
3*24*3600	"3d"	ThreeDays,
7*24*3600	"1w"	OneWeek,
30*24*3600	"1M"	OneMonth
        */
        public KlineInterval periodConvert(int period)
        {
            switch (period)
            {
                case 60:
                    return KlineInterval.OneMinute;
                case 3 * 60:
                    return KlineInterval.ThreeMinutes;
                case 5 * 60:
                    return KlineInterval.FiveMinutes;
                case 15 * 60:
                    return KlineInterval.FifteenMinutes;
                case 30 * 60:
                    return KlineInterval.ThirtyMinutes;
                case 3600: return KlineInterval.OneHour;
                case 2 * 3600: return KlineInterval.TwoHours;
                case 4 * 3600: return KlineInterval.FourHours;
                case 6 * 3600: return KlineInterval.SixHours;
                case 8 * 3600: return KlineInterval.EightHours;
                case 12 * 3600: return KlineInterval.TwelveHours;
                case 24 * 3600: return KlineInterval.OneDay;
                case 3 * 24 * 3600: return KlineInterval.ThreeDays;
                case 7 * 24 * 3600: return KlineInterval.OneWeek;
                case 30 * 24 * 3600: return KlineInterval.OneMonth;
                default:
                    throw new ArgumentException();
            }
        }

        public async Task<IConvertible> BuyAsync(decimal Amount, decimal Price = -1)
        {
            return await CreateOrder(Amount, SideDirection.Buy, Price);
        }
        public async Task<IConvertible> SellAsync(decimal Price, decimal Amount)
        {
            return await CreateOrder(Amount, SideDirection.Sell, Price);
        }
        public async Task<IConvertible> Buy_CloseSellAsync(decimal Price, decimal Amount)
        {
            return await CreateOrder(Amount, SideDirection.CloseSell, Price);
        }
        public async Task<IConvertible> Sell_CloseBuyAsync(decimal Price, decimal Amount)
        {
            return await CreateOrder(Amount, SideDirection.CloseBuy, Price);
        }
        public readonly static IReadOnlyDictionary<string, int> amountStepSizeDic = new Dictionary<string, int>() {
               { "BTCUSDT",3},
{  "ETHUSDT",3},
{  "BCHUSDT",3},
{  "XRPUSDT",1},
{  "EOSUSDT",1},
{  "LTCUSDT",3},
{  "TRXUSDT",0},
{  "ETCUSDT",2},
{  "LINKUSDT",2},
{  "XLMUSDT",0},
{  "ADAUSDT",0},
{  "XMRUSDT",3},
{  "DASHUSDT",3},
{  "ZECUSDT",3},
{  "XTZUSDT",1},
{  "BNBUSDT",2},
{  "ATOMUSDT",2},
{  "ONTUSDT",1},
{  "IOTAUSDT",1},
{  "BATUSDT",1},
{  "VETUSDT",0},
{  "NEOUSDT",2},
{  "QTUMUSDT",1},
{  "IOSTUSDT",0},
{  "THETAUSDT",1},
{  "ALGOUSDT",1},
{  "ZILUSDT",0},
{  "KNCUSDT",0},
{  "ZRXUSDT",1},
{  "COMPUSDT",3},
{  "OMGUSDT",1},
{  "DOGEUSDT",0},
{  "SXPUSDT",1},
{  "KAVAUSDT",1},
{  "BANDUSDT",1},
{  "RLCUSDT",1},
{  "WAVESUSDT",1},
{  "MKRUSDT",3},
{  "SNXUSDT",1},
{  "DOTUSDT",1},
{  "DEFIUSDT",3},
{  "YFIUSDT",3},
{  "BALUSDT",1},
{  "CRVUSDT",1},
{  "TRBUSDT",1},
{  "RUNEUSDT",0},
{  "SUSHIUSDT",0},
{  "SRMUSDT",0},
{  "EGLDUSDT",1},
{  "SOLUSDT",0},
{  "ICXUSDT",0},
{  "STORJUSDT",0},
{  "BLZUSDT",0},
{  "UNIUSDT",0},
{  "AVAXUSDT",0},
{  "FTMUSDT",0},
{  "HNTUSDT",0},
{  "ENJUSDT",0},
{  "FLMUSDT",0},
{  "TOMOUSDT",0},
{  "RENUSDT",0},
{  "KSMUSDT",1},
{  "NEARUSDT",0},
{  "AAVEUSDT",1},
{  "FILUSDT",1},
{  "RSRUSDT",0},
{  "LRCUSDT",0},
{  "MATICUSDT",0},
{  "OCEANUSDT",0},
{  "CVCUSDT",0},
{  "BELUSDT",0},
{  "CTKUSDT",0},
{  "AXSUSDT",0},
{  "ALPHAUSDT",0},
{  "ZENUSDT",1},
{  "SKLUSDT",0},
{  "GRTUSDT",0},
{  "1INCHUSDT",0},
{  "BTCBUSD",3},
{  "AKROUSDT",0},
{  "CHZUSDT",0},
{  "SANDUSDT",0},
{  "ANKRUSDT",0},
{  "LUNAUSDT",0},
{  "BTSUSDT",0},
{  "LITUSDT",1},
{  "UNFIUSDT",1},
{  "DODOUSDT",1},
{  "REEFUSDT",0},
{  "RVNUSDT",0},
{  "SFPUSDT",0},
{  "XEMUSDT",0},
{  "BTCSTUSDT",1},
{  "COTIUSDT",0},
{  "CHRUSDT",0},
{  "MANAUSDT",0},
{  "ALICEUSDT",1},
{  "HBARUSDT",0},
{  "ONEUSDT",0},
{  "LINAUSDT",0},
{  "STMXUSDT",0},
{  "DENTUSDT",0},
{  "CELRUSDT",0},
{  "HOTUSDT",0},
{  "MTLUSDT",0},
{  "OGNUSDT",0},
{  "NKNUSDT",0},
{  "SCUSDT",0},
{  "DGBUSDT",0},
{  "1000SHIBUSDT",0},
{  "ICPUSDT",2},
{  "BAKEUSDT",0},
{  "GTCUSDT",1},
{  "ETHBUSD",3},
{  "BTCDOMUSDT",3},
{  "TLMUSDT",0},
{  "BNBBUSD",2},
{  "ADABUSD",0},
{  "XRPBUSD",1},
{  "IOTXUSDT",0},
{  "DOGEBUSD",0},
{  "AUDIOUSDT",0},
{  "RAYUSDT",1},
{  "C98USDT",0},
{  "MASKUSDT",0},
{  "ATAUSDT",0},
{  "SOLBUSD",0},
{  "FTTBUSD",1},
{  "DYDXUSDT",1},
{  "1000XECUSDT",0},
{  "GALAUSDT",0},
{  "CELOUSDT",1},
{  "ARUSDT",1},
{  "KLAYUSDT",1},
{  "ARPAUSDT",0},
{  "CTSIUSDT",0},
{  "LPTUSDT",1},
{  "ENSUSDT",1},
{  "PEOPLEUSDT",0},
{  "ANTUSDT",1},
{  "ROSEUSDT",0},
{  "DUSKUSDT",0},
{  "FLOWUSDT",1},
{  "IMXUSDT",0},
{  "API3USDT",1},
{  "ANCUSDT",1},
{  "GMTUSDT",0},
{  "APEUSDT",0},
{  "BTCUSDT_220624",3},
{  "ETHUSDT_220624",3},
{  "BNXUSDT",1},
{  "WOOUSDT",0},
{  "FTTUSDT",1},
{  "JASMYUSDT",0},
{  "DARUSDT",1},
{  "GALUSDT",0}
        };
        //Precision
        public decimal getAmount(decimal amount)
        {

            var pre = 3;
            switch (Currency)
            {
                case "BNBUSDT":
                    pre = 2;
                    break;
                case "DOTUSDT":
                    pre = 1;
                    break;
                case "ADAUSDT":
                    pre = 0;
                    break;
                case "UNIUSDT":
                    pre = 0;
                    break;
                case "LINKUSDT":
                    pre = 2;
                    break;
                case "LUNAUSDT"://
                    pre = 0;
                    break;
                case "CHAUSDT":
                    pre = 0;
                    break;
                case "EOSUSDT":
                    pre = 1;
                    break;
                case "ATOMUSDT":
                    pre = 2;
                    break;
                default:
                    pre = amountStepSizeDic.GetValueOrDefault(Currency, pre);
                    break;
            }
            var re = Math.Round(amount, pre, MidpointRounding.ToNegativeInfinity);
            if (re <= 0)
            {
                log.Info("仓位过小：", amount);
                if (isTest)
                {
                    re = 0.001m;
                }
            }
            return re;
        }
        public decimal getPrice(decimal price)
        {
            return Math.Round(price, 4, MidpointRounding.ToNegativeInfinity);
        }
        public async Task<IConvertible> CreateOrder(decimal amount, SideDirection sideDirection, decimal price = -1, LogInfo info = null)
        {
            var req = new CreateOrderRequest();
            try
            {
                req.Quantity = getAmount(amount);
                if (price == -1)
                {
                    req.Type = OrderType.Market;
                }
                else
                {
                    req.Type = OrderType.Limit;//
                    req.Price = price;
                    req.TimeInForce = TimeInForce.GTC;
                }

                req.Symbol = Currency;

                req.Side = (sideDirection is SideDirection.CloseSell or SideDirection.Buy) ? OrderSide.Buy : OrderSide.Sell;
                req.PositionSide = (sideDirection is SideDirection.CloseBuy or SideDirection.Buy) ? CreateOrderRequest.PositionSideType.LONG : CreateOrderRequest.PositionSideType.SHORT;
                req.NewOrderResponseType = NewOrderResponseType.Acknowledge;
                req.NewClientOrderId = info?.ActId;
                if (isTest)
                {
                    await client.CreateTestOrder(req);
                    var testId = $"{Name}_{currency}_test_{req.GetHashCode()}";
                    //resp.OrderId;
                    //log.Debug("交易信息test：", new { req, testId }, msg3: LogType);
                    if (string.IsNullOrWhiteSpace(req.NewClientOrderId))
                        req.NewClientOrderId = testId;
                    log.InfoByOrder("订单创建完成", this, sideDirection, amount, price, req.NewClientOrderId, null, testId, info);
                    return testId;
                }
                var resp = await client.CreateOrder(req);
                if (string.IsNullOrWhiteSpace(req.NewClientOrderId))
                    req.NewClientOrderId = resp.OrderId + "";
                //log.Info("订单创建完成：", new { req, resp }, msg3: LogType);
                log.InfoByOrder("订单创建完成", this, sideDirection, amount, price, req.NewClientOrderId, null, resp.OrderId + "", info);
                return resp.OrderId;
            }
            catch (System.Exception ex)
            {
                //log.Error("交易异常:" + ex, new { req }, msg3: LogType);
                log.ErrorByOrder(ex, this, sideDirection, amount, price, info?.ActId, null, info);
                return null;
            }
        }
        ILog log;
        public async Task<bool> CancelOrderAsync(IConvertible id)
        {
            var re = await client.CancelOrder(new BinanceExchange.API.Models.Request.CancelOrderRequest() { OrderId = id.ToInt64(new NumberFormatInfo()) });

            return re.OrderId > 0;
        }
        public Order GetOrder(IConvertible id)
        {
            return new Order();
        }

        public Position[] GetPosition(string contractType)
        {
            var accountInfo = jfclient.account();
            var pstInfos = accountInfo.Positions.Where(p => p.Symbol == contractType).ToArray();
            var psts = new List<Position>();
            foreach (var pstInfo in pstInfos)
            {
                var pst = new Position();
                pst.Amount = pstInfo.positionAmt;
                pst.ContractType = pstInfo.Symbol;
                //pst.FrozenAmount
                pst.MarginLevel = pstInfo.Leverage;
                pst.Price = pstInfo.EntryPrice;
                pst.Profit = pstInfo.UnrealizedProfit;
                switch (pstInfo.PositionSide)
                {
                    case "LONG":
                        pst.Type = SideDirection.Buy;
                        break;
                    case "SHORT":
                        pst.Type = SideDirection.Sell;
                        break;
                    case "BOTH":
                        pst.Type = SideDirection.Buy;
                        break;
                }
                psts.Add(pst);
            }

            return psts.ToArray();
        }
    }
}
